www.gusucode.com > VC++ SkinCtrls窗体常用控件换肤程序-源码程序 > VC++ SkinCtrls窗体常用控件换肤程序-源码程序/code/SkinWindows/SkinCtrl.cpp

    // SkinCtrl.cpp: implementation of the CSkinCtrl class.
// Download by http://www.NewXing.com
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"

#include "SkinCtrlmgr.h"
#include "SkinCtrl.h"
#include "SkinCtrls.h"
#include "skinglobals.h"
#include "..\shared\wclassdefines.h"
#include "..\shared\winclasses.h"
#include "..\shared\delayredraw.h"
#include "..\shared\SysColors.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

// globals -- no other way using windows hooks.
static CSkinCtrl*	g_pCtrl = NULL;
static HHOOK		g_hMsgHook = NULL;
static int			g_nScrollbar = -1;

const int CORNER = 4;
const int EDGE = 1;
const int SCROLLCX = ::GetSystemMetrics(SM_CXHSCROLL);

CFont CSkinCtrl::s_fontMarlett2;
ISkinCtrlRender* CSkinCtrl::s_pRenderer = NULL;
BOOL CSkinCtrl::s_bDrawingEnabled = TRUE;

//////////////////////////////////////////////////////////////////////

CSkinCtrl::CSkinCtrl() : 
	m_pGlobals(NULL), 
	m_dwBaseStyle(SKCS_NOEDGE), 
	m_dwCtrlStyle(0), 
	m_bPreMFCSubclass(FALSE),
	m_bDragging(FALSE)
{
}

CSkinCtrl::~CSkinCtrl()
{
}

void CSkinCtrl::SetGlobals(CSkinGlobals* pGlobals)
{
	m_pGlobals = pGlobals;

//	if (IsValid())
//		Refresh();
}

COLORREF CSkinCtrl::GetBkgndColor() // controls background color
{ 
	switch (EdgeStyle())
	{
		case SKCS_NOEDGE:
		case SKCS_CLIENTEDGE:
			return GetColor(COLOR_WINDOW);

		case SKCS_BTNEDGE:
			return GetColor(COLOR_3DFACE); 

		case SKCS_ETCHEDEDGE:
			return GetParentBkgndColor();

		default:
			ASSERT (0);
			return -1;
	}
} 

COLORREF CSkinCtrl::GetColor(int nColor)
{
	return m_pGlobals ? m_pGlobals->GetColor(nColor) : CSkinGlobals::GetColorSt(nColor);
}

BOOL CSkinCtrl::IsChild(CSkinCtrl* pSkin)
{
	if (!pSkin)
		return FALSE;

	return ::IsChild(m_hWndHooked, pSkin->m_hWndHooked);
}

BOOL CSkinCtrl::AttachWindow(CWnd* pWnd) 
{ 
	m_bPreMFCSubclass = (CWnd::FromHandlePermanent(pWnd->GetSafeHwnd()) == NULL);
	
	if (HookWindow(*pWnd))
	{
		m_bDialog = CWinClasses::IsClass(*pWnd, WC_DIALOGBOX);
		m_dwOrgStyle = pWnd->GetStyle(); // original window style

		// init
		OnSetIni();

		// this forces WM_NCCALCSIZE to be sent to the ctrl
		// so that it can define its new border
		pWnd->SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); 

		return TRUE;
	}
	
	// else
	return FALSE;
}

// special treatment needed for popup windows
BOOL CSkinCtrl::IsPopup()
{
	// else
	return (::GetParent(GetHwnd()) == ::GetDesktopWindow());
}

void CSkinCtrl::Refresh(BOOL bUpdateWindow) 
{ 
	Invalidate(); 

	if (bUpdateWindow)
		UpdateWindow(GetHwnd());

	SendMessage(WM_NCPAINT);
}

BOOL CSkinCtrl::ModifyStyle(DWORD dwStylesToRemove, DWORD dwStylesToAdd)
{
	DWORD dwStyle = m_dwCtrlStyle;

	m_dwCtrlStyle |= dwStylesToAdd;
	m_dwCtrlStyle &= ~dwStylesToRemove;

	if (dwStyle != m_dwCtrlStyle)
	{
		Invalidate();
		SendMessage(WM_NCPAINT);
	}

	return TRUE;
}

BOOL CSkinCtrl::DetachWindow() 
{ 
	HWND hwnd = GetHwnd();

	if (!hwnd)
		return TRUE; // ?? done already

	CWnd* pWndPerm = CWnd::FromHandlePermanent(hwnd);
	CWnd* pCtrl = pWndPerm ? pWndPerm : CWnd::FromHandle(hwnd);
	ASSERT (pCtrl);

	DWORD dwStyle = pCtrl->GetStyle();
	
	// if we hooked this window PRIOR to MFC subclassing via DDX_Control,
	// then MFC will assert when it destructs the attached control because
	// the HWND does not get correctly detached presumably because the WndProc
	// has been so mucked about. so we unsubclass and then resubclass to keep 
	// MFC happy
	if (pWndPerm && m_bPreMFCSubclass)
		pWndPerm->UnsubclassWindow();
	
	// do children first
	if (pCtrl && pCtrl->GetSafeHwnd())
	{
		CWnd* pChild = pCtrl->GetWindow(GW_CHILD); 
		
		while (pChild)    
		{
			CSkinCtrlMgr::Instance().Unskin(*pChild);
			pChild = pChild->GetNextWindow();
		}
	}
	
	BOOL bRes = HookWindow((HWND)NULL); 
	
	if (pWndPerm && m_bPreMFCSubclass)
		pWndPerm->SubclassWindow(hwnd);
	
	// restore original window style
	if (::IsWindow(*pCtrl))
	{
		if (pCtrl && pCtrl->GetSafeHwnd())
			pCtrl->ModifyStyle(dwStyle, m_dwOrgStyle);
	}
	
	return bRes;
}

LRESULT CSkinCtrl::WindowProc(HWND hRealWnd, UINT msg, WPARAM wp, LPARAM lp)
{
	ASSERT(hRealWnd);

	if (s_bDrawingEnabled)
		return OnMsg(msg, wp, lp);
	else
		return CSubclassWnd::WindowProc(hRealWnd, msg, wp, lp);
}

LRESULT CSkinCtrl::OnMsg(UINT msg, WPARAM wp, LPARAM lp)
{
	UINT uRes = 0;
	LRESULT lr = 0;
	CWnd* pThis = GetCWnd();

	// this is an extra check because if we set the msg hook in response
	// to an edit box whose scrollbars we disabled then the standard way
	// does not seem to work :(
	if (g_hMsgHook && !(GetAsyncKeyState(MK_LBUTTON) & 0x8000))
	{
		// uninstall hook.
		::UnhookWindowsHookEx(g_hMsgHook);
		g_hMsgHook = NULL;
		g_pCtrl = NULL;
		g_nScrollbar = -1;
	}
	
	switch (msg) 
	{
	case WM_HSCROLL:
	case WM_VSCROLL:
		{
			UINT uSBCode = LOWORD(wp);
			int nPos = HIWORD(wp);

			// if lParam is NULL then its from one of 'this' control's scroll bars
			if (!lp)
				OnScroll(uSBCode, nPos, (msg == WM_HSCROLL));
			else
				CSkinCtrlMgr::Instance().DoScroll((HWND)lp, uSBCode, nPos, (msg == WM_HSCROLL)); 
		}
		// drop thru
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_CHAR:
		{
			CWindowDC dc(GetCWnd());
			CRect rWindow;
			GetDrawRect(rWindow);
			DrawScrollbars(&dc, rWindow);
		}
		break;
		
	case WM_PAINT: 
		if (WantsDraw(SKCS_PAINT))
		{
			CPaintDC dc(GetCWnd());
			
			if (OnPaint(&dc))
				lr = 0;
			else
				lr = CSubclassWnd::WindowProc(GetHwnd(), msg, (WPARAM)dc.GetSafeHdc(), lp);

			return lr;
		}
		break;
		
	case WM_PRINT: 
		if (lp & PRF_NONCLIENT)
		{
			// base class first
			CSubclassWnd::WindowProc(GetHwnd(), msg, wp, lp);

			if (IsPopup())
			{
				CDC* pDC = CDC::FromHandle((HDC)wp);
				
				CRect rWindow;
				GetDrawRect(rWindow);
				COLORREF color = GetColor(COLOR_3DSHADOW);

				pDC->Draw3dRect(rWindow, color, color);
				DrawScrollbars(pDC, rWindow, SB_BOTH);

				return 0;
			}
			else if (WantsDraw(SKCS_NCPAINT))
			{
				CDC* pDC = CDC::FromHandle((HDC)wp);
				
				OnNcPaint(pDC);
				
				return 0;
			}
		}
		break;
		
	case WM_NCPAINT: 
		if (IsPopup())
		{
			// base class first
			CSubclassWnd::WindowProc(GetHwnd(), msg, wp, lp);

			CWindowDC dc(GetCWnd());
			
			CRect rWindow;
			GetDrawRect(rWindow);
			COLORREF color = GetColor(COLOR_3DSHADOW);

			dc.Draw3dRect(rWindow, color, color);
			DrawScrollbars(&dc, rWindow, SB_BOTH);

			return 0;
		}
		else if (WantsDraw(SKCS_NCPAINT))
		{
			CWindowDC dc(GetCWnd());
			
			if (!WantsDraw(SKCS_NOBASENCPAINT))
			{
				if (wp > 1) // ie a real rgn
				{
					// make sure the scrollbars are included in the draw
					HRGN hRgn = GetScrollTrackRgn();

					if (hRgn)
						::CombineRgn((HRGN)wp, (HRGN)wp, hRgn, RGN_OR);

					::DeleteObject(hRgn);
				}

				CSubclassWnd::WindowProc(GetHwnd(), msg, wp, lp);
			}

			// do our painting over the top
			OnNcPaint(&dc);
			
			return 0;
		}
		break;
		
	case WM_ERASEBKGND: 
		if (WantsDraw(SKCS_ERASEBKGND))
		{
			CDC* pDC = CDC::FromHandle((HDC)wp);
			
			if (OnEraseBkgnd(pDC))
				return TRUE;
		}
		break;

	case WM_DRAWITEM:
		{
			LPDRAWITEMSTRUCT pDIS = (LPDRAWITEMSTRUCT)lp;

			// this really gets a bit tricky
			if (pDIS->CtlType != ODT_MENU)
			{
				CSkinCtrl* pSkin = CSkinCtrlMgr::Instance().GetSkinCtrl(pDIS->hwndItem);

				if (pSkin)
				{
					if (pSkin->WantsDraw(SKCS_DRAWITEM))
					{
						if (CSkinCtrlMgr::Instance().DoDrawItem(pDIS->hwndItem, pDIS))
							return TRUE; 
					}
					else if (pSkin->WantsDraw(SKCS_POSTDRAWITEM))
					{
						BOOL bRes =	CSubclassWnd::WindowProc(GetHwnd(), msg, wp, lp);

						if (pSkin->OnDrawItem(pDIS))
							return TRUE; 
						else
							return bRes;
					}
				}
			}
		}
		break;

	case WM_NCLBUTTONDOWN:
		// when scrolling window controls do alot of 'under the covers' redrawing which
		// we need to overdraw with our scrollbars.
		// the only of doing this is a global message hook for the duration of the scrolling
		if (wp == HTVSCROLL || wp == HTHSCROLL)
		{
			g_pCtrl = this;
			
			if (g_hMsgHook != NULL)
				::UnhookWindowsHookEx(g_hMsgHook);

			g_nScrollbar = (wp == HTVSCROLL ? SB_VERT : SB_HORZ);
			g_hMsgHook = SetWindowsHookEx(WH_MSGFILTER,	MsgWndProc, NULL, ::GetCurrentThreadId());
		}
		break;
		
	case WM_NCLBUTTONUP:
		// remove the global message hook for scrolling
		if (g_hMsgHook)
		{
			::UnhookWindowsHookEx(g_hMsgHook);
			g_hMsgHook = NULL;
			g_pCtrl = NULL;
			g_nScrollbar = -1;
		}
		break;
		
	case WM_NCCALCSIZE:
		lr = Default();

		// popups have simple borders for now
		if (!IsPopup() && WantsDraw(SKCS_NCPAINT) && wp)
		{
			OnNcCalcSize(&((LPNCCALCSIZE_PARAMS)lp)->rgrc[0]);
		}
		return lr;
		
	case WM_NOTIFY:
		{
			NMHDR* pNMHDR = (NMHDR*)lp;
			
			if (pNMHDR->code == NM_CUSTOMDRAW)
			{
				lr = CSkinCtrlMgr::Instance().DoCustomDraw(pNMHDR->hwndFrom, pNMHDR);
				
				if (m_bDialog)
					SetWindowLong(GetHwnd(), DWL_MSGRESULT, lr);
			}
			else
			{
				lr = Default();

				CSkinCtrlMgr::Instance().DoNotifyReflect(pNMHDR->hwndFrom, pNMHDR, lr);
				CSkinCtrlMgr::Instance().DoNotify(*pThis, pNMHDR); // safe version
			}
			return lr;
		}
	
	case WM_COMMAND:
		{
			UINT uMsg = HIWORD(wp);

			if (uMsg > 1)
			{
				lr = Default();

				CSkinCtrlMgr::Instance().DoNotifyReflect((HWND)lp, uMsg, lr); 
				CSkinCtrlMgr::Instance().DoNotify(*pThis, uMsg, LOWORD(wp), CWnd::FromHandle((HWND)lp)); // safe version

				return lr;
			}
		}
		break;

	case WM_SIZE:
		if (OnSize(GET_X_LPARAM(lp), GET_Y_LPARAM(lp)))
			return 0;
		break;
		
	case WM_MOVE:
		if (OnMove(GET_X_LPARAM(lp), GET_Y_LPARAM(lp)))
			return 0;
		break;
		
	case WM_TIMER:
		OnTimer(wp);
		break;
		
	case WM_KILLFOCUS:
	case WM_SETFOCUS:
		SendMessage(WM_NCPAINT);
		OnSetFocus(msg == WM_SETFOCUS);
		break;

	case WM_STYLECHANGED:
		{
			LPSTYLESTRUCT pSS = (LPSTYLESTRUCT)lp;
			OnStyleChanged(wp == GWL_EXSTYLE, pSS->styleOld, pSS->styleNew);
		}
		break;
		
	case WM_CTLCOLORBTN:
	case WM_CTLCOLORDLG:
	case WM_CTLCOLOREDIT:
	case WM_CTLCOLORLISTBOX:
	case WM_CTLCOLORSCROLLBAR:
	case WM_CTLCOLORSTATIC:
		{
			return (LRESULT)CSkinCtrlMgr::Instance().DoCtlColor((HWND)lp, CDC::FromHandle((HDC)wp));
		}
		break;

	case WM_ENABLE:
		{
			SetRedraw(FALSE);
			lr = Default();
			SetRedraw(TRUE);

			Refresh(TRUE);
			return lr;
		}
		break;
	}
	
	// We don't handle it: pass along
	return Default();//CSubclassWnd::WindowProc(GetHwnd(), msg, wp, lp);
}

void CSkinCtrl::OnScroll(UINT nSBCode, UINT nPos, BOOL bHorz)
{
	switch (nSBCode)
	{
	case SB_THUMBTRACK:
		m_bDragging = TRUE;
		break;

	default:
		m_bDragging = FALSE;
		break;
	}
}

void CSkinCtrl::OnUpdateHotspots(LPPOINT pOldCursor, LPPOINT pNewCursor, UINT uOldHitTest, UINT uNewHitTest)
{
	// if the user is in the middle of scrolling
	if (g_hMsgHook)
		return;

	ASSERT (pOldCursor || pNewCursor);

	// if the application has set a render callback 
	// we also check the entire window area
	if (s_pRenderer)
	{
		if (uNewHitTest != uOldHitTest && (uNewHitTest == HTNOWHERE || uOldHitTest == HTNOWHERE))
		{
			CWindowDC dc(GetCWnd());
			OnNcPaint(&dc);
			return; // in which case we don't need to paint anything else
		}
	}

	// default just checks for mouse over scrollbuttons
	DWORD dwStyle = GetStyle();

	// only interested in windows with scrollbars
	if (dwStyle & WS_HSCROLL || dwStyle & WS_VSCROLL)
	{
		CWindowDC dc(GetCWnd());

		// is or was the mouse in the scrollbar areas ?
		if (uOldHitTest == HTVSCROLL || uOldHitTest == HTHSCROLL || uNewHitTest == HTVSCROLL || uNewHitTest == HTHSCROLL)
		{
			CRect rWindow;
			GetDrawRect(rWindow);

			if (pOldCursor && pNewCursor)
			{
				// has it moved on or off a scrollbutton
				CRect rHorz, rVert, rBack, rForward;
				BOOL bOn, bWasOn;

				GetScrollbarRects(rWindow, rHorz, rVert);
				GetScrollbarButtonRects(rHorz, SB_HORZ, rBack, rForward);

				bOn = rBack.PtInRect(*pNewCursor);
				bWasOn = rBack.PtInRect(*pOldCursor);

				if (bOn != bWasOn)
				{
					DrawScrollbars(&dc, rWindow, SB_BOTH);
					return;
				}

				// else try next
				bOn = rForward.PtInRect(*pNewCursor);
				bWasOn = rForward.PtInRect(*pOldCursor);

				if (bOn != bWasOn)
				{
					DrawScrollbars(&dc, rWindow, SB_BOTH);
					return;
				}

				// else try next
				GetScrollbarButtonRects(rVert, SB_VERT, rBack, rForward);

				bOn = rBack.PtInRect(*pNewCursor);
				bWasOn = rBack.PtInRect(*pOldCursor);

				if (bOn != bWasOn)
				{
					DrawScrollbars(&dc, rWindow, SB_BOTH);
					return;
				}

				// else try next
				bOn = rForward.PtInRect(*pNewCursor);
				bWasOn = rForward.PtInRect(*pOldCursor);

				if (bOn != bWasOn)
				{
					DrawScrollbars(&dc, rWindow, SB_BOTH);
					return;
				}
			}
			else
				DrawScrollbars(&dc, rWindow, SB_BOTH);
		}
		else if (uNewHitTest == HTNOWHERE) // mouse has left the window
		{
			CRect rWindow;
			GetDrawRect(rWindow);

			DrawScrollbars(&dc, rWindow, SB_BOTH);
		}
	}
}

BOOL CSkinCtrl::IsHot(LPRECT pRect, BOOL bScreenCoords)
{
	HWND hCapture = ::GetCapture();

	if (hCapture && hCapture != GetHwnd())
		return FALSE;

	CRect rect(pRect);

	if (!pRect)
		GetWindowRect(rect);

	else if (!bScreenCoords)
	{
		CRect rWindow;
		GetWindowRect(rWindow);
		rect.OffsetRect(rWindow.TopLeft());
	}

	return CSkinCtrlMgr::Instance().IsHot(rect);
}

void CSkinCtrl::OnNcCalcSize(LPRECT pClient)
{
	int nEdge = max(0, GetFrameThickness() - 2);

	::InflateRect(pClient, -nEdge, -nEdge);
}

BOOL CSkinCtrl::OnPaint(CDC* pDC)
{
	return FALSE;
}

void CSkinCtrl::OnNcPaint(CDC* pDC)
{
	CRect rWindow;
	GetDrawRect(rWindow);
	
	NcPaint(pDC, rWindow);
}

void CSkinCtrl::NcPaint(CDC* pDC, CRect rect)
{
	NcPaintBorder(pDC, rect, GetParentBkgndColor(), GetBkgndColor(), EdgeStyle());
			
	DrawScrollbars(pDC, rect);
}

void CSkinCtrl::NcPaintBorder(CDC* pDC, CRect rect, COLORREF crParentBack, COLORREF crBack, int nEdgeStyle, BOOL bIsNc)
{
	if (g_nScrollbar != -1)
		return;

	if (nEdgeStyle != SKCS_NOEDGE)
	{
		const int nEdge = EDGE * 2;
		
		// fill the gap between the nc and client areas
		if (bIsNc)
		{
			rect.DeflateRect(EDGE, EDGE);
			CRect rClient;
			GetDrawRect(NULL, rClient);
			
			// make sure on the right and bottom that we do not paint over the scroll bars
			DWORD dwStyle = GetStyle();
			
			if (dwStyle & WS_HSCROLL)
				rClient.bottom += SCROLLCX;
			
			if (dwStyle & WS_VSCROLL)
			{
				if (HasLeftScrollbar())
					rClient.left -= SCROLLCX;
				else
					rClient.right += SCROLLCX;
			}

			// simple filled rects
			pDC->FillSolidRect(rect.left, rect.top, rect.right, 
				max(nEdge, rClient.top - rect.top), crBack);
			
			pDC->FillSolidRect(rect.left, rect.top, 
				max(nEdge, rClient.left - rect.left), rect.bottom, crBack);
			
			pDC->FillSolidRect(min(rect.right - nEdge, rClient.right), rect.top, 
				max(nEdge, rect.right - rClient.right), rect.bottom, crBack);
			
			pDC->FillSolidRect(rect.left, min(rect.bottom - nEdge, rClient.bottom), 
				rect.right, max(nEdge, rect.bottom - rClient.bottom), crBack);
			
			rect.InflateRect(EDGE, EDGE);
		}

		if (!s_pRenderer)
			FillCorners(pDC, rect, crParentBack);

		int nCorner = min(CORNER, min(rect.Height() / 2, rect.Width() / 2)); 

		// special case: very small etched frames
		if (nEdgeStyle == SKCS_ETCHEDEDGE && (rect.Height() < 4 || rect.Width() < 4))
			nEdgeStyle = SKCS_CLIENTEDGE;
				
		Draw3dEdge(pDC, rect, nCorner, nEdgeStyle, GetState(rect));
	}
}

int CSkinCtrl::GetState(LPRECT pHotRect)
{
	int nState = IM_COLD;

	if (!IsWindowEnabled())
		nState = IM_DISABLED;

	else if (pHotRect && IsHot(pHotRect))
		nState = IM_HOT;

	return nState;
}

void CSkinCtrl::FillCorners(CDC* pDC, LPRECT pWindow, COLORREF crParentBack, int nCorners, int nEdgeStyle)
{
	if (!CORNER)
		return;

	// else
	const int nCorner = CORNER + 1;

	if (nEdgeStyle == -1)
		nEdgeStyle = EdgeStyle();

	int nExtra = (nEdgeStyle == SKCS_BTNEDGE) ? 1 : 0;
	
	// fill by rgn method
	CRgn rgnCorners, rgnRect;
	rgnCorners.CreateRoundRectRgn(pWindow->left, pWindow->top, pWindow->right, pWindow->bottom, nCorner, nCorner);
	rgnRect.CreateRectRgnIndirect(pWindow);

	rgnRect.CombineRgn(&rgnRect, &rgnCorners, RGN_DIFF);

	pDC->FillRgn(&rgnRect, GetColorBrush(crParentBack));
	
/*
	CBrush* pOld = pDC->SelectObject();
	
	pDC->SelectStockObject(NULL_PEN);

	CRect rWindow(pWindow);
	CPoint ptCorner[3];
	
	// top left
	ptCorner[0] = ptCorner[1] = ptCorner[2] = rWindow.TopLeft();
	ptCorner[1].y = ptCorner[0].y + nCorner;
	ptCorner[2].x = ptCorner[0].x + nCorner;

	if (nCorners & RC_TOPLEFT)
		pDC->Polygon(ptCorner, 3);
	
	// top right
	ptCorner[0].x = rWindow.right;
	ptCorner[1].x = ptCorner[0].x;
	ptCorner[2].x = ptCorner[0].x - nCorner;

	if (nCorners & RC_TOPRIGHT)
		pDC->Polygon(ptCorner, 3);
	
	// bottom right
	ptCorner[0] = rWindow.BottomRight();
	ptCorner[1].y = ptCorner[0].y - nCorner - 1;
	ptCorner[2].y = ptCorner[0].y;

	if (nCorners & RC_BOTTOMRIGHT)
		pDC->Polygon(ptCorner, 3);
	
	// bottom left
	ptCorner[0].x = rWindow.left;
	ptCorner[1].x = ptCorner[0].x;
	ptCorner[2].x = ptCorner[0].x + nCorner + 1;

	if (nCorners & RC_BOTTOMLEFT)
		pDC->Polygon(ptCorner, 3);
	
	pDC->SelectObject(pOld);
*/
}

BOOL CSkinCtrl::OnEraseBkgnd(CDC* pDC)
{
	CRect rClient;
	GetClientRect(rClient);
	EraseBkgnd(pDC, rClient);
	
	return TRUE;
}

void CSkinCtrl::EraseBkgnd(CDC* pDC, CRect rect)
{
	pDC->FillSolidRect(rect, GetBkgndColor());
}

HBRUSH CSkinCtrl::OnCtlColor(CDC* pDC)
{
	pDC->SetBkMode(TRANSPARENT);
	
	COLORREF crBkgnd = GetBkgndColor();
	pDC->SetBkColor(crBkgnd);
	pDC->SetTextColor(GetColor(COLOR_WINDOWTEXT));
	
	return (HBRUSH)GetColorBrush(crBkgnd)->GetSafeHandle();
}

void CSkinCtrl::GetDrawRect(LPRECT pWindow, LPRECT pClient, BOOL bExcludeScrollbars)
{
	CRect rWindow;
	GetWindowRect(rWindow);
	
	if (pClient)
	{
		GetClientRect(pClient);
		GetCtrl()->ClientToScreen(pClient);
		::OffsetRect(pClient, -rWindow.left, -rWindow.top);
		
		if (!bExcludeScrollbars)
		{
			DWORD dwStyle = GetStyle();
			
			if (dwStyle & WS_VSCROLL)
			{
				if (HasLeftScrollbar())
					pClient->left -= SCROLLCX;
				else
					pClient->right += SCROLLCX;
			}
			
			if (dwStyle & WS_HSCROLL)
				pClient->bottom += SCROLLCX;
		}
	}
	
	if (pWindow)
	{
		rWindow.OffsetRect(-rWindow.TopLeft());
		*pWindow = rWindow;
	}
}

int CSkinCtrl::GetFrameThickness() 
{ 
	if (IsPopup())
		return 2;

	else if (EdgeStyle() == SKCS_NOEDGE)
		return 0;

	else
		return EDGE + 2; 
}

BOOL CSkinCtrl::HasCtrlStyle(DWORD dwStyle, DWORD dwMask)
{
	if (dwMask == -1)
		dwMask = dwStyle;

	return ((m_dwCtrlStyle & dwMask) == dwStyle);
}

BOOL CSkinCtrl::HasLeftScrollbar()
{
	DWORD dwExStyle = GetCtrl()->GetExStyle();
	return (dwExStyle & WS_EX_LEFTSCROLLBAR);
}

BOOL CSkinCtrl::IsScrollbarEnabled(int nBar, BOOL bSpinButton)
{
	// Disabled scrollbar never have a thumb
	if (!IsWindowEnabled())
		return FALSE;      

	if (bSpinButton)
		return TRUE;
	
	// The minimal thumb size depends on the system version
	// For Windows 98 minimal thumb size is a half of scrollbar size 
	// and for Windows NT is always 8 pixels regardless of system metrics. 
	// I really don't know why.
	int nMinThumbSize;
	
	if (GetVersion() & 0x80000000) // Windows 98 code
		nMinThumbSize = SCROLLCX / 2;
	else                    
		nMinThumbSize = 8;

	CWnd* pWnd = GetCWnd();

	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;     
	pWnd->GetScrollInfo(nBar, &si );

	CRect rWindow, rScrollbar;

	GetWindowRect(rWindow);
	GetScrollbarRects(rWindow, nBar == SB_HORZ ? rScrollbar : NULL, nBar == SB_VERT ? rScrollbar : NULL);
	
	// Calculate the size and position of the thumb 
	int nRange = si.nMax - si.nMin + 1; 
	
	if (nRange)
	{
		CRect rThumb(rScrollbar);
		int nScrollArea = (nBar == SB_HORZ ? rScrollbar.Width() : rScrollbar.Height()) - 2 * SCROLLCX;
		
		int nThumbSize; 

		if (!si.nPage) // If nPage is not set then thumb has default size
			nThumbSize = GetSystemMetrics(SM_CXHTHUMB);
		else
			nThumbSize = max(MulDiv(si.nPage ,nScrollArea, nRange), nMinThumbSize);
		
		if (nThumbSize >= nScrollArea && nScrollArea > 0)
			return FALSE;
	}
	else
		return FALSE;

	return TRUE;
}

void CSkinCtrl::DrawScrollbars(CDC* pDC, CRect rWindow, int nBar)
{
	if (CSkinBase::IsThemingEnabled())
		return;

	DWORD dwStyle = GetStyle();
	BOOL bHorz = (dwStyle & WS_HSCROLL);
	BOOL bVert = (dwStyle & WS_VSCROLL);
	BOOL bDrawHorz = (nBar == SB_HORZ || nBar == SB_BOTH);
	BOOL bDrawVert = (nBar == SB_VERT || nBar == SB_BOTH);

	CPoint ptCursor;
	GetCursorPos(&ptCursor);
	
	if (bHorz && bVert)
	{
		CRect rHorz, rVert, rDead;
		GetScrollbarRects(rWindow, rHorz, rVert, rDead);

		if (bDrawHorz)
			DrawScrollBar(pDC, rHorz, SB_HORZ, &ptCursor);
		
		if (bDrawVert)
			DrawScrollBar(pDC, rVert, SB_VERT, &ptCursor);
		
		// draw the dead space
		if (nBar == SB_BOTH)
			pDC->FillSolidRect(rDead, GetBkgndColor());
	}
	else if (bVert && bDrawVert)
	{
		CRect rVert;
		GetScrollbarRects(rWindow, NULL, rVert);
		DrawScrollBar(pDC, rVert, SB_VERT, &ptCursor);
	}
	else if (bHorz && bDrawHorz)
	{
		CRect rHorz;
		GetScrollbarRects(rWindow, rHorz, NULL);
		DrawScrollBar(pDC, rHorz, SB_HORZ, &ptCursor);
	}
}

HRGN CSkinCtrl::GetScrollTrackRgn()
{
	CRect rVert, rHorz, rWindow;

	GetDrawRect(rWindow);
	GetScrollbarRects(rWindow, rHorz, rVert, NULL);

	if (rHorz.IsRectEmpty() && rVert.IsRectEmpty())
		return NULL; // has no scrollbars

	// else
	CRgn rgn;

	if (!rHorz.IsRectEmpty() && !rVert.IsRectEmpty())
	{
		CRgn rgnTemp;

		rgn.CreateRectRgnIndirect(rHorz);
		rgnTemp.CreateRectRgnIndirect(rVert);

		rgn.CombineRgn(&rgn, &rgnTemp, RGN_OR);
		
		rgnTemp.DeleteObject();
	}
	else if (!rHorz.IsRectEmpty())
	{
		rgn.CreateRectRgnIndirect(rHorz);
	}
	else // if (!rVert.IsRectEmpty())
	{
		rgn.CreateRectRgnIndirect(rVert);
	}

	return (HRGN)rgn.Detach();
}

void CSkinCtrl::GetScrollbarRects(CRect rWindow, LPRECT pHorz, LPRECT pVert, LPRECT pDeadSpace)
{
	int nFrameSize = GetFrameThickness(); 
	
	DWORD dwStyle = GetStyle();
	BOOL bHorz = (dwStyle & WS_HSCROLL);
	BOOL bVert = (dwStyle & WS_VSCROLL);
	BOOL bLeftScrollbar = HasLeftScrollbar();

	// init
	if (pHorz)
		SetRectEmpty(pHorz);

	if (pVert)
		SetRectEmpty(pVert);

	if (pDeadSpace)
		SetRectEmpty(pDeadSpace);

	if (bHorz && bVert)
	{
		if (pHorz)
		{
			pHorz->left = rWindow.left + nFrameSize + (bLeftScrollbar ? SCROLLCX : 0); 
			pHorz->top = rWindow.bottom - nFrameSize - SCROLLCX;
			pHorz->right = rWindow.right - nFrameSize - (bLeftScrollbar ? 0 : SCROLLCX); 
			pHorz->bottom = rWindow.bottom - nFrameSize;
		}
		
		if (pVert)
		{
			pVert->left = bLeftScrollbar ? (rWindow.left + nFrameSize) : 
										(rWindow.right - nFrameSize - SCROLLCX); 
			pVert->top = rWindow.top + nFrameSize;
			pVert->right = pVert->left + SCROLLCX; 
			pVert->bottom = rWindow.bottom - nFrameSize - SCROLLCX;
		}

		if (pDeadSpace)
		{
			pDeadSpace->left = bLeftScrollbar ? (rWindow.left + nFrameSize) : 
										(rWindow.right - nFrameSize - SCROLLCX); 
			pDeadSpace->right = pDeadSpace->left + SCROLLCX; 
			pDeadSpace->top = rWindow.bottom - nFrameSize - SCROLLCX;
			pDeadSpace->bottom = pDeadSpace->top + SCROLLCX;
		}
	}
	else if (bVert && pVert)
	{
		pVert->left = bLeftScrollbar ? (rWindow.left + nFrameSize) : 
										(rWindow.right - nFrameSize - SCROLLCX); 
		pVert->top = rWindow.top + nFrameSize;
		pVert->right = pVert->left + SCROLLCX; 
		pVert->bottom = rWindow.bottom - nFrameSize;
	}
	else if (bHorz && pHorz)
	{
		pHorz->left = rWindow.left + nFrameSize; 
		pHorz->top = rWindow.bottom - nFrameSize - SCROLLCX;
		pHorz->right = rWindow.right - nFrameSize; 
		pHorz->bottom = rWindow.bottom - nFrameSize;
	}
}

void CSkinCtrl::DrawScrollbarThumb(CDC* pDC, CRect rScrollbar, int nBar)
{
	// The minimal thumb size depends on the system version
	// For Windows 98 minimal thumb size is a half of scrollbar size 
	// and for Windows NT is always 8 pixels regardless of system metrics. 
	// I really don't know why.
	int nMinThumbSize;
	
	if (GetVersion() & 0x80000000) // Windows 98 code
		nMinThumbSize = SCROLLCX / 2;
	else                    
		nMinThumbSize = 8;

	CWnd* pWnd = GetCWnd();

	// Disabled scrollbar never have a thumb
	if (!IsWindowEnabled())
		return;      
	
	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_ALL;     
	pWnd->GetScrollInfo(nBar, &si );
	
	// Calculate the size and position of the thumb   
	int nRange = si.nMax - si.nMin + 1; 
	
	if (nRange)
	{
		CRect rThumb(rScrollbar);
		int nScrollArea = (nBar == SB_HORZ ? rScrollbar.Width() : rScrollbar.Height()) - 2 * SCROLLCX;
		
		int nThumbSize; 

		if (!si.nPage) // If nPage is not set then thumb has default size
			nThumbSize = GetSystemMetrics(SM_CXHTHUMB);
		else
			nThumbSize = max(MulDiv(si.nPage ,nScrollArea, nRange), nMinThumbSize);
		
		if (nThumbSize >= nScrollArea)
		{
			nThumbSize = nScrollArea;
			return;
		}
		
		int nThumbPos;
		if (UINT(nRange) == si.nPage)
		{
			nThumbPos = 0;
			nThumbSize--;
		}
		else
			nThumbPos = MulDiv(si.nPos - si.nMin, nScrollArea - nThumbSize, nRange - si.nPage);
		
		if (nBar == SB_VERT)
		{
			rThumb.top += SCROLLCX + nThumbPos;
			rThumb.bottom = rThumb.top + nThumbSize;
		}
		else // SB_HORZ
		{
			rThumb.left += SCROLLCX + nThumbPos;
			rThumb.right = rThumb.left + nThumbSize;
		}
		
		if (nThumbSize <= nScrollArea) // Don't draw the thumb when it's larger than the scroll area
		{
			pDC->FillSolidRect(rThumb, 0);
			pDC->Draw3dRect(rThumb, GetColor(COLOR_3DHILIGHT), GetColor(COLOR_3DSHADOW));
		}
	}   
}

void CSkinCtrl::DrawScrollBar(CDC* pDC, CRect rScrollbar, int nBar, LPPOINT pCursor)
{   
	BOOL bMouseDown = (!m_bDragging && pCursor) ? (GetAsyncKeyState(MK_LBUTTON) & 0x8000) : FALSE;
	
	// Calculate the arrow rectangles
	CRect rc1 = rScrollbar, rc2 = rScrollbar; 
	GetScrollbarButtonRects(rScrollbar, nBar, rc1, rc2);
		
	// Draw the scrollbar arrows
	DrawScrollbarButton(pDC, rc1, nBar, TRUE, (bMouseDown && rc1.PtInRect(*pCursor)), GetBkgndColor());
	DrawScrollbarButton(pDC, rc2, nBar, FALSE, (bMouseDown && rc2.PtInRect(*pCursor)), GetBkgndColor());
}

void CSkinCtrl::GetScrollbarButtonRects(CRect rScrollbar, int nBar, LPRECT pBack, LPRECT pForward)
{
	int nScrollSize = SCROLLCX;
	
	if (nBar == SB_HORZ)
	{
		if ((rScrollbar.Width()) < 2 * nScrollSize)
			nScrollSize = rScrollbar.Width() / 2;
		
		if (pBack)
		{
			*pBack = rScrollbar;
			pBack->right = pBack->left + nScrollSize;
		}
		
		if (pForward)
		{
			*pForward = rScrollbar;
			pForward->left = pForward->right - nScrollSize;
		}
	}
	else // SB_VERT
	{
		if ((rScrollbar.Height()) < 2 * nScrollSize)
			nScrollSize = rScrollbar.Height() / 2;
		
		if (pBack)
		{
			*pBack = rScrollbar;
			pBack->bottom = pBack->top + nScrollSize;      
		}
		
		if (pForward)
		{
			*pForward = rScrollbar;
			pForward->top = pForward->bottom - nScrollSize;
		}
	}   
}

void CSkinCtrl::DrawScrollbarButton(CDC* pDC, CRect rButton, int nBar, BOOL bBack, BOOL bDown, COLORREF crParentBackColor, BOOL bSpinButton)
{
	// determine state
	BOOL bEnabled = IsScrollbarEnabled(nBar, bSpinButton);

	int nState = IM_COLD;
	
	if (!bEnabled)
		nState = IM_DISABLED;

	else if (!m_bDragging)
	{
		if (bDown)
			nState = IM_DOWN;
		
		else if (IsHot(rButton))
			nState = IM_HOT;
	}

	BOOL bClipped = bSpinButton || (nBar == SB_HORZ && rButton.Width() < SCROLLCX) || 
									(nBar == SB_VERT && rButton.Height() < SCROLLCX);

	BOOL bTextured = (GetControlBitmap(bClipped ? SKCB_SPINUP : SKCB_SCROLLUP, 0, 0, 
										bClipped ? -1 : SKCB_SCROLLALL) != NULL);
	
	CDC dcMem;
	dcMem.CreateCompatibleDC(pDC);
	
	CRect rMem(rButton);
	rMem.OffsetRect(-rMem.TopLeft());
	
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(pDC, rButton.Width(), rButton.Height());
	CBitmap* pOldBM = dcMem.SelectObject(&bmp);

	if (bTextured)
	{
		dcMem.FillSolidRect(rMem, crParentBackColor);

		int nCtl = SKCB_SCROLLALL;
		COLORREF crMask;

		if (bClipped)
		{
			if (nBar == SB_HORZ)
				nCtl = bBack ? SKCB_SPINLEFT : SKCB_SPINRIGHT;
			else
				nCtl = bBack ? SKCB_SPINUP : SKCB_SPINDOWN;
		}
		else
		{
			if (nBar == SB_HORZ)
				nCtl = bBack ? SKCB_SCROLLLEFT : SKCB_SCROLLRIGHT;
			else
				nCtl = bBack ? SKCB_SCROLLUP : SKCB_SCROLLDOWN;
		}

		CDC dcMem2;
		dcMem2.CreateCompatibleDC(pDC);

		CBitmap* pBM = GetControlBitmap(nCtl, nState, &crMask, SKCB_SCROLLALL);
		CBitmap* pOld = dcMem2.SelectObject(pBM);

		BITMAP bm;
		pBM->GetBitmap(&bm);

		if (bm.bmHeight != rMem.bottom || bm.bmWidth != rMem.right)
		{
			CSkinBase::StretchBlt(&dcMem, 0, 0, rMem.right, rMem.bottom, &dcMem2, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY, crMask);
		}
		else
		{
			CSkinBase::BitBlt(&dcMem, 0, 0, rMem.right, rMem.bottom, &dcMem2, 0, 0, SRCCOPY, crMask);
		}

		if (nBar == SB_HORZ)
		{
//			if (bClipped)
//				rMem.left += (bBack ? -1 : 1);
//			else
//				rMem.left += (bBack ? -2 : 3);
		}

		dcMem2.SelectObject(pOld);
		dcMem2.DeleteDC();
	}
	else
	{
		// border
		if (!s_pRenderer || !s_pRenderer->Draw3dRect(&dcMem, rMem, SKCS_BTNEDGE, nState, GetColor(COLOR_3DFACE)))
		{
			int nStyle = (bDown ? SKCS_CLIENTEDGE : SKCS_BTNEDGE);

			int nCorner = !bSpinButton ? SCROLLCX / 2 : min(CORNER, min(rMem.right, rMem.bottom) / 2);
			COLORREF crBack = GetColor((nState == IM_DOWN || nState == IM_HOT) ? COLOR_3DHILIGHT : COLOR_3DFACE);

			dcMem.FillSolidRect(rMem, crParentBackColor);

			dcMem.SelectStockObject(NULL_PEN);
			CBrush* pOldBr = dcMem.SelectObject(GetColorBrush(crBack));

			if (!bSpinButton)
				dcMem.Ellipse(rMem.left, rMem.top, rMem.right + 1, rMem.bottom + 1);
			else
				dcMem.RoundRect(rMem.left, rMem.top, rMem.right + 1, rMem.bottom + 1, nCorner * 2, nCorner * 2);

			dcMem.SelectObject(pOldBr);

			if (!bClipped) // standard round buttons
			{
				Draw3dEdge(&dcMem, rMem, nCorner, nStyle, nState);
			}
			else
			{
				DWORD dwFlags = RC_EDGES | RC_THIN | (bDown ? RC_DOWN : RC_UP);
				DWORD dwCorners = 0;
				int nClipping = 0;

				if (nBar == SB_HORZ)
				{
					dcMem.FillSolidRect((bBack ? nCorner : 0), 0,
										rMem.right - (bBack ? 0 : nCorner),	rMem.bottom,
										crBack);

					nClipping = (bBack ? ISKCR_CLIPRIGHT : ISKCR_CLIPLEFT);
				}
				else
				{
					dcMem.FillSolidRect(0, (bBack ? nCorner : 0),
										rMem.right,	rMem.bottom - (bBack ? 0 : nCorner),
										crBack);

					nClipping = (bBack ? ISKCR_CLIPBOTTOM : ISKCR_CLIPTOP);
				}

				// border
				Draw3dEdge(&dcMem, rMem, nCorner, nStyle, nState, nClipping);

//				if (nBar == SB_HORZ)
//					rMem.left += (bBack ? -2 : 3);
			}
		}
	}

	if (!bTextured || !GetControlBitmap(SKCB_SCROLLUP))
	{
		if (nBar == SB_HORZ)
			rMem.left += (bBack ? -2 : 3);

		else if (bClipped && !bBack)
			rMem.top -= 2;

		char cArrow = (nBar == SB_HORZ) ? (bBack ? 0x77 : 0x38) : (bBack ? 0x74 : 0x75);

		COLORREF colorText = GetColor(bEnabled ? COLOR_WINDOWTEXT : COLOR_GRAYTEXT);
		dcMem.SetTextColor(colorText);

		if (bDown)
			rMem.OffsetRect(1, 1);
		
		dcMem.SetBkMode(TRANSPARENT);
		CFont* pOld = dcMem.SelectObject(GetFont(SBFONT_MARLETT));
		dcMem.DrawText(cArrow, rMem, DT_VCENTER | DT_CENTER | DT_SINGLELINE);
		dcMem.SelectObject(pOld);
	}
	
	pDC->BitBlt(rButton.left, rButton.top, rButton.Width(), rButton.Height(), &dcMem, 0, 0, SRCCOPY);
	
	dcMem.SelectObject(pOldBM);
	dcMem.DeleteDC();
	bmp.DeleteObject();
}

void CSkinCtrl::Draw3dEdge(CDC* pDC, LPRECT pRect, int nRadius, int nEdge, int nState, int nClipping)
{
	CRect rect(pRect);

	if (s_pRenderer)
	{
		if ((nClipping == ISKCR_CLIPNONE) && (nRadius == (pRect->right - pRect->left) / 2))
		{
			if (s_pRenderer->Draw3dRoundEdge(pDC, pRect, nEdge, nState))
				return;
		}
		else if (s_pRenderer->Draw3dEdge(pDC, pRect, nEdge, nState, nClipping))
			return;
	}

	DWORD dwEdges = RC_ALL & 
					((nClipping & ISKCR_CLIPLEFT) ? ~RC_LEFT : RC_ALL) &
					((nClipping & ISKCR_CLIPRIGHT) ? ~RC_RIGHT : RC_ALL) &
					((nClipping & ISKCR_CLIPTOP) ? ~RC_TOP : RC_ALL) &
					((nClipping & ISKCR_CLIPBOTTOM) ? ~RC_BOTTOM : RC_ALL);

	// else
	switch (nEdge)
	{
	case SKCS_BTNEDGE:
		CRoundCorner::Draw(pDC, rect, nRadius, dwEdges, RC_EDGES | RC_THIN | RC_UP);
		break;
		
	case SKCS_CLIENTEDGE:
		CRoundCorner::Draw(pDC, rect, nRadius, dwEdges, RC_EDGES | RC_THIN | RC_DOWN);
		break;
		
	case SKCS_ETCHEDEDGE:
		if (nRadius < 2)
		{
			BOOL bHorz = (rect.Height() <= nRadius);
			
			bHorz ? rect.bottom-- : rect.right--;
			pDC->FillSolidRect(rect, GetColor(COLOR_3DSHADOW));
			
			rect.OffsetRect(bHorz ? 0 : 1, bHorz ? 1 : 0);
			pDC->FillSolidRect(rect, GetColor(COLOR_3DHILIGHT));
		}
		else // box
		{
			rect.right--;
			rect.bottom--;
			
			pDC->SelectStockObject(NULL_BRUSH);
			rect.OffsetRect(1, 1);
			
			// highlight first then shadow, else 2 of the corners look wrong
			CPen* pOldPen = pDC->SelectObject(GetColorPen(COLOR_3DHIGHLIGHT));
			pDC->RoundRect(rect, CPoint(nRadius, nRadius));
			
			rect.OffsetRect(-1, -1);
			pOldPen = pDC->SelectObject(GetColorPen(COLOR_3DSHADOW));
			pDC->RoundRect(rect, CPoint(nRadius, nRadius));
			pDC->SelectObject(pOldPen);
		}
	}
}

void CSkinCtrl::DrawDropButton(CDC* pDC, CRect rButton, LPPOINT pCursor, COLORREF crParentBackColor)
{
	int nDC = pDC->SaveDC();

	rButton.bottom = rButton.top + SCROLLCX;
	
	// pressed?
	CPoint ptCursor;
	
	BOOL bMouseDown = (GetAsyncKeyState(MK_LBUTTON) & 0x8000);
	
	if (bMouseDown)
	{
		if (!pCursor)
			GetCursorPos(&ptCursor);
		else
			ptCursor = *pCursor;
	}
	
	// textured?
	if (GetControlBitmap(SKCB_SCROLLDROPDOWN))
	{
		int nState = (bMouseDown && rButton.PtInRect(ptCursor)) ? IM_DOWN : IsHot(rButton) ? IM_HOT : IM_COLD;
		
		CDC dcMem, dcMem2;
		dcMem.CreateCompatibleDC(pDC);
		dcMem2.CreateCompatibleDC(pDC);
		
		CRect rMem(rButton);
		rMem.OffsetRect(-rMem.TopLeft());
		
		CBitmap bmp;
		bmp.CreateCompatibleBitmap(pDC, rButton.Width(), rButton.Height());
		CBitmap* pOldBM = dcMem.SelectObject(&bmp);

		dcMem.FillSolidRect(rMem, crParentBackColor);

		COLORREF crMask;
		CBitmap* pBM = GetControlBitmap(SKCB_SCROLLDROPDOWN, nState, &crMask);
		CBitmap* pOldBM2 = dcMem2.SelectObject(pBM);

		BITMAP bm;
		pBM->GetBitmap(&bm);

		// if the mask is -1 then we assume that no transparency is required
		if (bm.bmHeight != rMem.bottom || bm.bmWidth != rMem.right)
		{
			CSkinBase::StretchBlt(&dcMem, 0, 0, rMem.right, rMem.bottom, &dcMem2, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY, crMask);
		}
		else
		{
			CSkinBase::BitBlt(&dcMem, 0, 0, rMem.right, rMem.bottom, &dcMem2, 0, 0, SRCCOPY, crMask);
		}

		pDC->BitBlt(rButton.left, rButton.top, rButton.Width(), rButton.Height(), &dcMem, 0, 0, SRCCOPY);

		dcMem.SelectObject(pOldBM);
		dcMem.DeleteDC();
		dcMem2.SelectObject(pOldBM2);
		dcMem2.DeleteDC();
		bmp.DeleteObject();
	}
	else
		DrawScrollbarButton(pDC, rButton, SB_VERT, FALSE, bMouseDown && rButton.PtInRect(ptCursor), crParentBackColor);
	
	pDC->RestoreDC(nDC);
}

void CSkinCtrl::DrawSpinButton(CDC* pDC, CRect rButton, LPPOINT pCursor, BOOL bVert, COLORREF crParentBackColor)
{
	// pressed?
	CPoint ptCursor;
	
	BOOL bMouseDown = (GetAsyncKeyState(MK_LBUTTON) & 0x8000);
	
	if (bMouseDown)
	{
		if (!pCursor)
			GetCursorPos(&ptCursor);
		else
			ptCursor = *pCursor;
	}

	CDC dcMem;
	dcMem.CreateCompatibleDC(pDC);
	
	CRect rMem(rButton);
	ptCursor.Offset(-rMem.TopLeft());
	rMem.OffsetRect(-rMem.TopLeft());
	
	CBitmap bmp;
	bmp.CreateCompatibleBitmap(pDC, rButton.Width(), rButton.Height());
	CBitmap* pOldBM = dcMem.SelectObject(&bmp);

	// draw left/up arrow
	CRect rHalf = rMem;

	if (bVert)
		rHalf.bottom = (rHalf.top + rHalf.bottom) / 2;
	else
		rHalf.right = (rHalf.left + rHalf.right) / 2;

	BOOL bDown = bMouseDown && rHalf.PtInRect(ptCursor);
	DrawScrollbarButton(&dcMem, rHalf, bVert ? SB_VERT : SB_HORZ, TRUE, bDown, crParentBackColor, TRUE);

	// draw right/down arrow
	if (bVert)
	{
		rHalf.top = rHalf.bottom;
		rHalf.bottom = rMem.bottom;
	}
	else
	{
		rHalf.left = rHalf.right;
		rHalf.right = rMem.right;
	}

	bDown = bMouseDown && !bDown && rHalf.PtInRect(ptCursor);
	DrawScrollbarButton(&dcMem, rHalf, bVert ? SB_VERT : SB_HORZ, FALSE, bDown, crParentBackColor, TRUE);

	pDC->BitBlt(rButton.left, rButton.top, rButton.Width(), rButton.Height(), &dcMem, 0, 0, SRCCOPY);
	
	dcMem.SelectObject(pOldBM);
	dcMem.DeleteDC();
	bmp.DeleteObject();
}

void CSkinCtrl::DrawPushButton(CDC* pDCMem, CRect rButton, int nState, int nCorner, COLORREF crParentBackColor, BOOL bClipLeft, BOOL bClipRight)
{
	int nEdge = (nState == IM_DOWN) ? SKCS_CLIENTEDGE : SKCS_BTNEDGE;
	int nClipping = (bClipLeft ? ISKCR_CLIPLEFT : 0) | (bClipRight ? ISKCR_CLIPRIGHT : 0);

	if (!s_pRenderer || !s_pRenderer->Draw3dRect(pDCMem, rButton, nEdge, nState, GetBkgndColor(), nClipping))
	{
		COLORREF crBackColor = (nState == IM_HOT || nState == IM_DOWN) ? 
							CRoundCorner::Lighter(GetBkgndColor(), 0.05f) : GetBkgndColor();

		if (bClipLeft && bClipRight)
			pDCMem->FillSolidRect(rButton, crBackColor);
		else
		{
			if (crParentBackColor != -1)
				pDCMem->FillSolidRect(rButton, crParentBackColor);
			
			pDCMem->SelectStockObject(NULL_PEN);
			CBrush* pOldBr = pDCMem->SelectObject(GetColorBrush(crBackColor));
			
			pDCMem->RoundRect(rButton.left, rButton.top, rButton.right + 1, rButton.bottom + 1, 
				nCorner * 2, nCorner * 2);
			
			pDCMem->SelectObject(pOldBr);
			
			// clipping
			if (bClipLeft)
				pDCMem->FillSolidRect(rButton.left, rButton.top, nCorner, rButton.Height(), crBackColor);
			
			else if (bClipRight)
				pDCMem->FillSolidRect(rButton.right - nCorner, rButton.top, nCorner, rButton.Height(), crBackColor);
		}
		
		// Draw the raised/sunken edges of the button (unless flat)
		DWORD dwCorners = (bClipLeft ? 0 : RC_LEFT) | (bClipRight ? 0 : RC_RIGHT);
		DWORD dwStyle = RC_EDGES | RC_THIN | (nState == IM_DOWN ? RC_DOWN : RC_UP);

		Draw3dEdge(pDCMem, rButton, nCorner, nEdge, dwStyle, nClipping);
	}
}

void CSkinCtrl::DrawPushButton(CDC* pDCMem, CRect rButton, CBitmap* pBMLeft, CBitmap* pBMMid, CBitmap* pBMRight, COLORREF crParentBackColor, COLORREF crMask)
{
	ASSERT (pBMLeft && pBMLeft->GetSafeHandle() && 
			pBMMid && pBMMid->GetSafeHandle() && 
			pBMRight && pBMRight->GetSafeHandle());

	if (!(pBMLeft && pBMLeft->GetSafeHandle() && 
			pBMMid && pBMMid->GetSafeHandle() && 
			pBMRight && pBMRight->GetSafeHandle()))
	{
		return;
	}

	CRect rMid = rButton, rBitmap;
	
	if (crMask != -1 && crParentBackColor != -1)
		pDCMem->FillSolidRect(rButton, crParentBackColor);
	
	CBitmap* pOld = NULL;
	CDC dcMem2;
	dcMem2.CreateCompatibleDC(pDCMem); // to hold the bitmaps

	// left part of button
	BITMAP bm;
	pBMLeft->GetBitmap(&bm);

	rBitmap.SetRect(rButton.left, 
					rButton.top, 
					min(rButton.left + bm.bmWidth, rButton.left + rButton.Width() / 2), 
					rButton.bottom);

	rMid.left = rBitmap.right;

	pOld = dcMem2.SelectObject(pBMLeft);
	DrawPushButtonBitmap(pDCMem, &dcMem2, rBitmap, pBMLeft, FALSE, crMask);
	dcMem2.SelectObject(pOld);

	// right part of button
	pBMRight->GetBitmap(&bm);

	rBitmap.SetRect(max(rButton.right - bm.bmWidth, rButton.right - rButton.Width() / 2), 
					rButton.top, 
					rButton.right, 
					rButton.bottom);
	rMid.right = rBitmap.left;
	
	pOld = dcMem2.SelectObject(pBMRight);
	DrawPushButtonBitmap(pDCMem, &dcMem2, rBitmap, pBMRight, FALSE, crMask);
	dcMem2.SelectObject(pOld);
	
	// middle part of button
	pBMMid->GetBitmap(&bm);
	
	pOld = dcMem2.SelectObject(pBMMid);
	DrawPushButtonBitmap(pDCMem, &dcMem2, rMid, pBMMid, TRUE, crMask);
	dcMem2.SelectObject(pOld);
		
	dcMem2.DeleteDC();
}

void CSkinCtrl::DrawPushButtonBitmap(CDC* pDCMem, CDC* pDCMem2, CRect rBitmap, CBitmap* pBitmap, BOOL bMid, COLORREF crMask)
{
	BITMAP bm;
	pBitmap->GetBitmap(&bm);

	// normally buttons are stretched horizontally but not vertically which is
	// why we require left:mid:right bitmaps.

	// however, we need to be able to handle a vertically stretched button 
	// without distorting the bitmap (> bitmap height)

	CBitmap* pOld = pDCMem2->SelectObject(pBitmap);

	if (rBitmap.Height() == bm.bmHeight)
	{
		CSkinBase::BitBlt(pDCMem, rBitmap.left, rBitmap.top, rBitmap.Width(), rBitmap.Height(), 
								pDCMem2, 0, 0, SRCCOPY, crMask);
	}
	else if (rBitmap.Height() < bm.bmHeight)
	{
		CSkinBase::StretchBlt(pDCMem, rBitmap.left, rBitmap.top, rBitmap.Width(), rBitmap.Height(), 
								pDCMem2, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY, crMask);
	}
	else // split the bitmap vertically and stretch the middle few pixels
	{
		int nMid = 2 + bm.bmHeight % 2;
		int nTop = bm.bmHeight / 2 - 1;
		int nBottom = bm.bmHeight - nTop - nMid;

		// top
		CSkinBase::StretchBlt(pDCMem, rBitmap.left, rBitmap.top, 
							bMid ? rBitmap.Width() : bm.bmWidth, nTop, 
							pDCMem2, 0, 0, bm.bmWidth, nTop, SRCCOPY, crMask);

		// mid
		CSkinBase::StretchBlt(pDCMem, rBitmap.left, rBitmap.top + nTop, 
							bMid ? rBitmap.Width() : bm.bmWidth, rBitmap.Height() - (nTop + nBottom), 
							pDCMem2, 0, nTop, bm.bmWidth, nMid, SRCCOPY, crMask);

		// bottom
		CSkinBase::StretchBlt(pDCMem, rBitmap.left, rBitmap.bottom - nBottom, 
							bMid ? rBitmap.Width() : bm.bmWidth, nBottom, 
							pDCMem2, 0, nTop + nMid, bm.bmWidth, nBottom, SRCCOPY, crMask);
	}
}

void CSkinCtrl::CalcTextRect(CDC* pDC, CString sText, CRect& rText, UINT nAlign)
{
	CRect rCalc(rText);

	UINT nVAlign = (nAlign & (DT_VCENTER | DT_TOP | DT_BOTTOM));
	nAlign &= ~(DT_VCENTER | DT_TOP | DT_BOTTOM);

	int nHeight = pDC->DrawText(sText, rCalc, nAlign | DT_CALCRECT);

	if (rText.Height() > nHeight)
	{
		if (nVAlign & DT_VCENTER)
			rText.DeflateRect(0, (rText.Height() - nHeight) / 2);

		else if (nVAlign & DT_BOTTOM)
			rText.top = rText.bottom - nHeight;

		else // top
			rText.bottom = rText.top + nHeight;
	}

	if (rText.Width() > rCalc.Width())
	{
		if (nAlign & DT_CENTER)
			rText.DeflateRect((rText.Width() - rCalc.Width()) / 2, 0);

		else if (nAlign & DT_RIGHT)
			rText.left = rText.right - rCalc.Width();

		else // left
			rText.right = rText.left + rCalc.Width();
	}
}

COLORREF CSkinCtrl::GetParentBkgndColor()
{
	HWND hParent = GetParent();

	if (!hParent)
		return GetColor(COLOR_3DFACE);

	CSkinCtrl* pSkin = CSkinCtrlMgr::Instance().GetSkinCtrl(hParent);
	
	// if the parent is not skinned then return 3dface
	if (!pSkin)
		return GetColor(COLOR_3DFACE);

	else // retur the parent skins backcolor
		return pSkin->GetBkgndColor();
}

CWnd* CSkinCtrl::GetChildWnd(LPCTSTR szClass)
{
	CWnd* pChild = GetCtrl()->GetWindow(GW_CHILD); 
	
	while (pChild)    
	{
		if (CWinClasses::IsClass(*pChild, szClass))
			return pChild;
		
		pChild = pChild->GetNextWindow();
	}
	
	return NULL;
}

void CSkinCtrl::ClipChildren(CDC* pDC)
{
	CWnd* pChild = GetCtrl()->GetWindow(GW_CHILD); 
	
	while (pChild)    
	{
		if (pChild->IsWindowVisible())
		{
			CString sClassName(CWinClasses::GetClass(*pChild));
			BOOL bChildOK = TRUE;
			
			// flat toolbars
			if (sClassName.CompareNoCase(WC_TOOLBAR) == 0)
			{
				UINT uStyle = pChild->GetStyle();
				
				if (uStyle & TBSTYLE_FLAT)
					bChildOK = FALSE;
			}
			
			// group buttons
			else if (sClassName.CompareNoCase(WC_BUTTON) == 0)
			{
				UINT uStyle = pChild->GetStyle();
				
				if ((uStyle & 0x0F) == BS_GROUPBOX)
					bChildOK = FALSE;
			}
			
			// static frames
			else if (sClassName.CompareNoCase(WC_STATIC) == 0)
			{
				UINT uStyle = pChild->GetStyle();
				
				if ((uStyle & SS_BLACKFRAME) || (uStyle & SS_GRAYFRAME) || (uStyle & SS_WHITEFRAME))
					bChildOK = FALSE;
			}
			
			// dialogs
			else if (sClassName.CompareNoCase(WC_DIALOGBOX) == 0)
			{
				bChildOK = FALSE;
			}
			
			// we've got a valid window so clip it
			if (bChildOK)
			{
				CRect rChild;
				pChild->GetWindowRect(&rChild);
				pChild->ScreenToClient(&rChild);
				pChild->MapWindowPoints(GetCWnd(), rChild);

				pDC->ExcludeClipRect(&rChild);
			}
		}
		
		pChild = pChild->GetNextWindow();
	}
}

LRESULT CALLBACK CSkinCtrl::MsgWndProc(int code, WPARAM wp, LPARAM lp)
{
	if (g_pCtrl)
	{
		MSG& msg = *((MSG*)lp);
		
		if (g_pCtrl->OnMsgWndProc(msg))
			return TRUE;
	}
	
	// else
	return CallNextHookEx(g_hMsgHook, code, wp, lp);
}

BOOL CSkinCtrl::OnMsgWndProc(MSG& msg)
{
	ASSERT_VALID(GetCWnd());
	
	int code = msg.message;
	
	if (!(GetAsyncKeyState(MK_LBUTTON) & 0x8000))
	{
		// uninstall hook.
		::UnhookWindowsHookEx(g_hMsgHook);
		g_hMsgHook = NULL;
		g_pCtrl = NULL;
		g_nScrollbar = -1;
	}
	
	SendMessage(WM_NCPAINT);
	CSkinCtrlMgr::Instance().UpdateCursorPos();
	
	return FALSE; // not handled
}

////////////////////////////////////////////////////////////////////////////////////////////////
// static helpers because friend status is not transferable

CSkinCtrl* CSkinCtrl::GetSkinCtrl(CWnd* pCtrl) 
{ 
	return GetSkinCtrl(*pCtrl); 
}

CSkinCtrl* CSkinCtrl::GetSkinCtrl(HWND hCtrl) 
{ 
	return CSkinCtrlMgr::Instance().GetSkinCtrl(hCtrl); 
}

BOOL CSkinCtrl::Skin(HWND hCtrl)
{ 
	return CSkinCtrlMgr::Instance().Skin(hCtrl); 
}

LRESULT CSkinCtrl::DoCustomDraw(HWND hCtrl, NMHDR* pNMHDR)
{
	return CSkinCtrlMgr::Instance().DoCustomDraw(hCtrl, pNMHDR); 
}

LRESULT CSkinCtrl::DoNotifyReflect(HWND hCtrl, NMHDR* pNMHDR, LRESULT lrParent)
{
	return CSkinCtrlMgr::Instance().DoNotifyReflect(hCtrl, pNMHDR, lrParent); 
}

void CSkinCtrl::DoNotifyReflect(HWND hCtrl, UINT uNotify, LRESULT lrParent)
{
	CSkinCtrlMgr::Instance().DoNotifyReflect(hCtrl, uNotify, lrParent); 
}

HBRUSH CSkinCtrl::DoCtlColor(HWND hCtrl, CDC* pDC)
{
	return CSkinCtrlMgr::Instance().DoCtlColor(hCtrl, pDC); 
}

void CSkinCtrl::DoScroll(HWND hCtrl, UINT nSBCode, UINT nPos, BOOL bHorz)
{
	 CSkinCtrlMgr::Instance().DoScroll(hCtrl, nSBCode, nPos, bHorz); 
}

////////////////////////////////////////////////////////////////////////////////////////////////

void CSkinCtrl::GetCursorPos(LPPOINT pCursor)
{
	ASSERT (pCursor);
	CRect rWindow;

	CSkinCtrlMgr::Instance().GetCursorPos(pCursor);
	GetWindowRect(rWindow);

	pCursor->x -= rWindow.left;
	pCursor->y -= rWindow.top;
}

BOOL CSkinCtrl::DelayRedraw(int nDelay)
{
	return CDelayRedraw::Start(GetHwnd(), nDelay);
}

CFont* CSkinCtrl::GetFont(int nFont)
{
	if (nFont == SBFONT_MARLETT)
	{
		if (!s_fontMarlett2.GetSafeHandle())
		{
			VERIFY(s_fontMarlett2.CreateFont(SCROLLCX - 3, 0, 0, 0,
					FW_THIN, 0, 0, 0, SYMBOL_CHARSET, 0, 0, 0, 0, _T("Marlett")));
		}

		return &s_fontMarlett2;
	}

	// else
	return m_pGlobals ? m_pGlobals->GetFont(nFont) : CSkinGlobals::GetFontSt(nFont);
}

CBrush* CSkinCtrl::GetColorBrush(COLORREF color)
{
	return m_pGlobals ? m_pGlobals->GetColorBrush(color) : CSkinGlobals::GetColorBrushSt(color);
}

CPen* CSkinCtrl::GetColorPen(COLORREF color, int nWidth)
{
	return m_pGlobals ? m_pGlobals->GetColorPen(color, nWidth) : CSkinGlobals::GetColorPenSt(color, nWidth);
}

CBrush* CSkinCtrl::GetColorBrush(int nColor)
{
	return m_pGlobals ? m_pGlobals->GetColorBrush(nColor) : CSkinGlobals::GetColorBrushSt(nColor);
}

CPen* CSkinCtrl::GetColorPen(int nColor, int nWidth)
{
	return m_pGlobals ? m_pGlobals->GetColorPen(nColor, nWidth) : CSkinGlobals::GetColorPenSt(nColor, nWidth);
}

CBitmap* CSkinCtrl::GetControlBitmap(int nItem, int nState, COLORREF* pMask, int nAltItem)
{
	return m_pGlobals ? m_pGlobals->GetControlBitmap(nItem, nState, pMask, nAltItem) : NULL;
}

void CSkinCtrl::AlignRect(CRect& rect, const CRect& rContainer, UINT nAlignment)
{
	CSize size = rect.Size();
	rect = rContainer;

	// vert align
	if (nAlignment & DT_VCENTER)
		rect.DeflateRect(0, (rect.Height() - size.cy) / 2);

	else if (nAlignment & DT_BOTTOM)
		rect.top = rect.bottom - size.cy;

	else // top
		rect.bottom = rect.top + size.cy;

	// horz align
	if (nAlignment & DT_CENTER)
		rect.DeflateRect((rect.Width() - size.cx) / 2, 0);

	else if (nAlignment & DT_RIGHT)
		rect.left = rect.right - size.cx;

	else // left
		rect.right = rect.left + size.cx;
}